
from OpenGL.GLU import gluProject
from math import sqrt, asin, degrees
import numpy

from amt.cg.core.util import cross_product, normalized


class Arcball:
    '''
    This class implements an Arcball object.
    '''
    def __init__(self, type="scene", center=[0,0,0], radius=1, camera=None):
        '''
        This method defines the constructor of the Arcball object.
        '''
        self.type = type
        self.camera = camera
        self.real_center = center
        
        if type == "scene":
            self.center = numpy.array(center)
            self.radius = radius
        else:
            # If it's an object, the center must be transformed to window coordinates.
            self.center = numpy.array(gluProject(*center))
            self.radius = self._get_projected_radius(radius)
        
        self.initial_point = numpy.zeros(3)
        self.final_point = numpy.zeros(3)       
        
    def set_initial_point(self, x, y):
        '''
        Sets the initial point of the arcball.
        '''
        self.initial_point[0] = x
        self.initial_point[1] = y
    
    def set_final_point(self, x, y):
        '''
        Sets the final point of the arcball.
        '''
        self.final_point[0] = x
        self.final_point[1] = y
        
    def rotate(self):
        '''
        This method implements a rotation. It returns the rotation axis
        and the angle between the vectors.
        '''
        self.center = numpy.array(gluProject(*self.real_center))
        
        # Finding the vectors.
        vector1 = self.initial_point - self.center
        vector2 = self.final_point - self.center
        
        # Calculating the distance from center to the points.
        module1 = sqrt(vector1[0]*vector1[0] + vector1[1]*vector1[1])
        module2 = sqrt(vector2[0]*vector2[0] + vector2[1]*vector2[1])
               
        # If a point is outside the sphere, projects it.
        if module1 > self.radius:
            self._project_vector(vector1)
        if module2 > self.radius:
            self._project_vector(vector2)
        
        # Calculating Z coordinates.
        vector1[2] = self._get_z_coord(module1)
        vector2[2] = self._get_z_coord(module2)
        
        # Recalculating both modules.
        module1 = sqrt(vector1[0]*vector1[0] + vector1[1]*vector1[1] + vector1[2]*vector1[2])
        module2 = sqrt(vector2[0]*vector2[0] + vector2[1]*vector2[1] + vector2[2]*vector2[2])
        
        # Finding the rotation axis.
        rotation_axis = cross_product(vector1, vector2)
        
        # Finding the angle between the vectors.
        rotation_axis_module = sqrt(rotation_axis[0]*rotation_axis[0] + 
                                   rotation_axis[1]*rotation_axis[1] +
                                   rotation_axis[2]*rotation_axis[2])
        angle = degrees(asin(rotation_axis_module / (module1*module2)))
                 
        return (rotation_axis, angle)
    
    def _get_projected_radius(self, real_radius):
        '''
        Returns the radius of the sphere projected.
        '''
        up = self.camera.up_vector
        module_up = sqrt(up[0]*up[0] + up[1]*up[1] + up[2]*up[2])
        unit_up = numpy.array(self.camera.up_vector) / module_up  
        
        # Finding a point in the border of the sphere
        border_point_real = self.real_center + (unit_up*real_radius)
        border_point_projected = gluProject(border_point_real[0], 
                                            border_point_real[1],
                                            border_point_real[2])
               
        # Calculating the distance between the border_point_projected and the center to discover the projected readius
        projected_radius = sqrt((border_point_projected[0] - self.center[0])*
                                (border_point_projected[0] - self.center[0]) + 
                                (border_point_projected[1] - self.center[1])*
                                (border_point_projected[1] - self.center[1]) +
                                (border_point_projected[2] - self.center[2])*
                                (border_point_projected[2] - self.center[2]))
        
        return projected_radius

    def _project_vector(self, vector):
        vector = normalized(vector)
        vector = [ coord * self.radius for coord in vector]

    def _get_z_coord(self, module):
        z2 = self.radius*self.radius - module
        if z2 < 0:
            return 0
        return sqrt(z2)
    